﻿/// <reference path="util/FileHelper.ts" />

class glRenderer
{
    constructor() { }
    gl: WebGLRenderingContext;
    canvas: HTMLCanvasElement;
    Pmatrix: WebGLUniformLocation;
    Vmatrix: WebGLUniformLocation;
    Mmatrix: WebGLUniformLocation;
    lightColor: WebGLUniformLocation;
    lightDirection: WebGLUniformLocation;
    lightEnvironment: WebGLUniformLocation;
    uniTexture: WebGLUniformLocation;
    attPosition: number;
    attColor: number;
    attTexture: number;
    attNormal: number;
    proj_matrix: Float32Array;
    mov_matrix: Float32Array;
    view_matrix: Float32Array;
    vertices: number[] = [];
    colors: number[] = [];
    indices: number[] = [];
    textureCoords: number[] = [];
    normals: number[] = [];
    objData: string;
    mtlData: string;
    vertex_buffer: WebGLBuffer;
    color_buffer: WebGLBuffer;
    texture_buffer: WebGLBuffer;
    normal_buffer: WebGLBuffer;
    index_buffer: WebGLBuffer;
    imgSource: string[] = [];
    imgIndex: number = 0;
    lastTime: number = 0;
    drawType: string = "";

    start()
    {
        //获取WebGL对象
        this.canvas = <HTMLCanvasElement>document.getElementById("glcanvas");
        this.canvas.width = 800;
        this.canvas.height = 800;

        this.gl = <WebGLRenderingContext>this.canvas.getContext("webgl", { antialias: true }) || <WebGLRenderingContext>this.canvas.getContext("experimental-webgl", { antialias: true });
        if (!this.gl)
        {
            alert("Unable to initialize WebGL. Your browser may not support it.");
            return;
        }
        
        this.imgSource.push("uv_grid_512.jpg");
        this.imgSource.push("grid.jpg");

        document.getElementById("drawSphere").onclick = (event) => { this.drawSphere(event); };
        document.getElementById("drawCylinder").onclick = (event) => { this.drawCylinder(event); };
        document.getElementById("changeTexture").onclick = (event) => { this.changeTexture(event); };
        document.getElementById("downloadModel").onclick = (event) => { this.downloadModel(event); };
        document.getElementById("loadModel").onchange = (event) => { this.loadModel(event); };

        this.gl.viewport(0.0, 0.0, this.canvas.width, this.canvas.height);
        this.gl.enable(this.gl.CULL_FACE);//遮挡剔除
        this.gl.clearColor(0.2, 0.2, 0.2, 1.0);
        this.gl.clearDepth(1.0);
        this.gl.clear(this.gl.COLOR_BUFFER_BIT | this.gl.DEPTH_BUFFER_BIT);
    }

    render()
    {
        var deltaTime = Date.now() - this.lastTime;
        this.lastTime = Date.now();

        this.gl.clearColor(0.2, 0.2, 0.2, 1.0);
        this.gl.clearDepth(1.0);
        this.gl.clear(this.gl.COLOR_BUFFER_BIT | this.gl.DEPTH_BUFFER_BIT);

        this.rotateY(this.mov_matrix, deltaTime * 0.001);
        this.gl.uniformMatrix4fv(this.Mmatrix, false, this.mov_matrix);

        this.gl.drawElements(this.gl.TRIANGLES, this.indices.length, this.gl.UNSIGNED_SHORT, 0);

        requestAnimationFrame(() => { this.render(); });
    }

    drawSphere(event: MouseEvent)
    {
        var r = parseFloat((document.getElementById("sr") as HTMLInputElement).value);
        var w = parseFloat((document.getElementById("sw") as HTMLInputElement).value);
        var h = parseFloat((document.getElementById("sh") as HTMLInputElement).value);
        
        this.createSphere(r, w, h);

        this.render();
    }

    drawCylinder(event: MouseEvent)
    {
        var r = parseFloat((document.getElementById("cr") as HTMLInputElement).value);
        var h = parseFloat((document.getElementById("ch") as HTMLInputElement).value);
        var n = parseFloat((document.getElementById("cn") as HTMLInputElement).value);
        
        this.createCylinder(r, h, n);

        this.render();
    }

    changeTexture(event: MouseEvent)
    {
        this.imgIndex = (this.imgIndex + 1) % this.imgSource.length;
        this.initTexture("res/" + this.imgSource[this.imgIndex]);
    }

    downloadModel(event: MouseEvent)
    {
        this.objData = "mtllib cylinder.mtl\n";
        for (var i = 0; i < this.vertices.length; i += 3)
        {
            this.objData += "v " + this.vertices[i].toFixed(6) + " " + this.vertices[i + 1].toFixed(6) + " " + this.vertices[i + 2].toFixed(6) + "\n";
        }
        for (var i = 0; i < this.textureCoords.length; i += 2)
        {
            this.objData += "vt " + this.textureCoords[i].toFixed(6) + " " + this.textureCoords[i + 1].toFixed(6) + "\n";
        }
        for (var i = 0; i < this.normals.length; i += 3)
        {
            this.objData += "vn " + this.normals[i].toFixed(6) + " " + this.normals[i + 1].toFixed(6) + " " + this.normals[i + 2].toFixed(6) + "\n";
        }
        this.objData += "usemtl mat1SG\n"
        for (var i = 0; i < this.indices.length; i += 3)
        {
            this.objData += "f " + (this.indices[i] + 1) + "/" + (this.indices[i] + 1) + "/" + (this.indices[i] + 1) +
                " " + (this.indices[i + 1] + 1) + "/" + (this.indices[i + 1] + 1) + "/" + (this.indices[i + 1] + 1) +
                " " + (this.indices[i + 2] + 1) + "/" + (this.indices[i + 2] + 1) + "/" + (this.indices[i + 2] + 1) + "\n";
        }

        this.mtlData = "newmtl mat1SG\n" +
            "Kd 0.00 0.00 0.00\n" +
            "Ka 0.00 0.00 0.00\n" +
            "Ks 0.00 0.00 0.00\n" +
            "Tf 1.00 1.00 1.00\n" +
            "Ni 1.00\n" +
            "map_Kd " + this.imgSource[this.imgIndex] + "\n";

        FileHelper.SaveStringFile("cylinder.obj", this.objData);
        FileHelper.SaveStringFile("cylinder.mtl", this.mtlData);
        FileHelper.SaveFile(this.imgSource[this.imgIndex], "res/" + this.imgSource[this.imgIndex]);
    }

    loadCount: number;
    objRes: string;
    mtlRes: string;
    imgRes: string;
    modelHeight: number;
    modelSize: number;
    loadModel(event: Event)
    {
        var inputFile = document.getElementById("loadModel") as HTMLInputElement;

        this.loadCount = inputFile.files.length;
        var objReader: FileReader;
        var mtlReader: FileReader;
        var imgReader: FileReader;
        for (var i = 0; i < inputFile.files.length; i++)
        {
            var file = inputFile.files[i] as File;
            if (file.name.indexOf(".obj") != -1)
            {
                objReader = new FileReader();
                objReader.readAsText(file);
                objReader.onload = (event) =>
                {
                    this.loadCount--;
                    this.objRes = objReader.result;
                }
            }
            else if (file.name.indexOf(".mtl") != -1)
            {
                mtlReader = new FileReader();
                mtlReader.readAsText(file);
                mtlReader.onload = (event) =>
                {
                    this.loadCount--;
                    this.mtlRes = mtlReader.result;
                }
            }
            else if (file.type.indexOf("image") != -1)
            {
                imgReader = new FileReader();
                imgReader.readAsDataURL(file);
                imgReader.onload = (event) =>
                {
                    this.loadCount--;
                    this.imgRes = imgReader.result;
                }
            }
        }

        this.checkLoadState();
    }

    checkLoadState()
    {
        if (this.loadCount > 0)
        {
            requestAnimationFrame(() => { this.checkLoadState(); });
        }
        else
        {
            this.createModel();
        }
    }

    getModelData()
    {
        this.vertices = [];
        this.textureCoords = [];
        this.normals = [];
        this.indices = [];
        this.colors = [];

        var maxX = 0;
        var minX = 0;
        var maxY = 0;
        var minY = 0;
        var maxZ = 0;
        var minZ = 0;

        var tempV: string[] = [];
        var tempVT: string[] = [];
        var tempVN: string[] = [];

        var objString = this.objRes.split("\n");
        for (var i = 0; i < objString.length; i++)
        {
            if (objString[i].length > 1)
            {
                var str = objString[i].split(" ");
                if (str[0] == "v")
                {
                    tempV.push(objString[i].substring(2));

                    var x = parseFloat(str[1]);
                    var y = parseFloat(str[2]);
                    var z = parseFloat(str[3]);
                    if (maxX == 0 && minX == 0)
                    {
                        maxX = x;
                        minX = x;
                    }
                    else
                    {
                        maxX = Math.max(maxX, x);
                        minX = Math.min(minX, x);
                    }
                    if (maxY == 0 && minY == 0)
                    {
                        maxY = y;
                        minY = y;
                    }
                    else
                    {
                        maxY = Math.max(maxY, y);
                        minY = Math.min(minY, y);
                    }
                    if (maxZ == 0 && minZ == 0)
                    {
                        maxZ = z;
                        minZ = z;
                    }
                    else
                    {
                        maxZ = Math.max(maxZ, z);
                        minZ = Math.min(minZ, z);
                    }
                }
                else if (str[0] == "vt")
                {
                    tempVT.push(objString[i].substring(3));
                }
                else if (str[0] == "vn")
                {
                    tempVN.push(objString[i].substring(3));
                }
            }
        }

        for (var i = 0; i < objString.length; i++)
        {
            if (objString[i].length > 1)
            {
                var str = objString[i].split(" ");
                if (str[0] == "f")
                {
                    for (var j = 2; j < str.length - 1; j++)
                    {
                        var i1 = parseInt(str[1].split("/")[0]) - 1;
                        var i2 = parseInt(str[j].split("/")[0]) - 1;
                        var i3 = parseInt(str[j + 1].split("/")[0]) - 1;
                        this.vertices.push(parseFloat(tempV[i1].split(" ")[0]), parseFloat(tempV[i1].split(" ")[1]), parseFloat(tempV[i1].split(" ")[2]));
                        this.vertices.push(parseFloat(tempV[i2].split(" ")[0]), parseFloat(tempV[i2].split(" ")[1]), parseFloat(tempV[i2].split(" ")[2]));
                        this.vertices.push(parseFloat(tempV[i3].split(" ")[0]), parseFloat(tempV[i3].split(" ")[1]), parseFloat(tempV[i3].split(" ")[2]));

                        var t1 = parseInt(str[1].split("/")[1]) - 1;
                        var t2 = parseInt(str[j].split("/")[1]) - 1;
                        var t3 = parseInt(str[j + 1].split("/")[1]) - 1;
                        this.textureCoords.push(parseFloat(tempVT[t1].split(" ")[0]), parseFloat(tempVT[t1].split(" ")[1]));
                        this.textureCoords.push(parseFloat(tempVT[t2].split(" ")[0]), parseFloat(tempVT[t2].split(" ")[1]));
                        this.textureCoords.push(parseFloat(tempVT[t3].split(" ")[0]), parseFloat(tempVT[t3].split(" ")[1]));

                        var n1 = parseInt(str[1].split("/")[2]) - 1;
                        var n2 = parseInt(str[j].split("/")[2]) - 1;
                        var n3 = parseInt(str[j + 1].split("/")[2]) - 1;
                        this.normals.push(parseFloat(tempVN[n1].split(" ")[0]), parseFloat(tempVN[n1].split(" ")[1]), parseFloat(tempVN[n1].split(" ")[2]));
                        this.normals.push(parseFloat(tempVN[n2].split(" ")[0]), parseFloat(tempVN[n2].split(" ")[1]), parseFloat(tempVN[n2].split(" ")[2]));
                        this.normals.push(parseFloat(tempVN[n3].split(" ")[0]), parseFloat(tempVN[n3].split(" ")[1]), parseFloat(tempVN[n3].split(" ")[2]));
                    }
                }
            }
        }

        //构造索引
        for (var i = 0; i < this.vertices.length / 3; i++)
        {
            this.indices.push(i);
        }

        this.modelHeight = maxY - minY;
        this.modelSize = Math.max(maxX - minX, maxY - minY, maxZ - minZ);

        //指定顶点颜色数据源
        for (var i = 0; i < this.vertices.length / 3; i++)
        {
            this.colors.push(1, 1, 1);
        }
    }

    createModel()
    {
        //制作着色器程序
        var vertShader = this.getShader("shader-vs");
        var fragShader = this.getShader("shader-fs");
        var shaderProgram = this.gl.createProgram();
        this.gl.attachShader(shaderProgram, vertShader);
        this.gl.attachShader(shaderProgram, fragShader);
        this.gl.linkProgram(shaderProgram);
        this.gl.useProgram(shaderProgram);

        //获取接口位置，并开启数组模式
        this.attPosition = this.gl.getAttribLocation(shaderProgram, "position");
        this.attTexture = this.gl.getAttribLocation(shaderProgram, "textureCoord");
        this.attColor = this.gl.getAttribLocation(shaderProgram, "color");
        this.attNormal = this.gl.getAttribLocation(shaderProgram, "normal");
        this.gl.enableVertexAttribArray(this.attPosition);
        this.gl.enableVertexAttribArray(this.attTexture);
        this.gl.enableVertexAttribArray(this.attColor);
        this.gl.enableVertexAttribArray(this.attNormal);

        this.getModelData();

        this.vertex_buffer = this.gl.createBuffer();
        this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.vertex_buffer);
        this.gl.bufferData(this.gl.ARRAY_BUFFER, new Float32Array(this.vertices), this.gl.STATIC_DRAW);
        this.gl.vertexAttribPointer(this.attPosition, 3, this.gl.FLOAT, false, 0, 0);

        this.normal_buffer = this.gl.createBuffer();
        this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.normal_buffer);
        this.gl.bufferData(this.gl.ARRAY_BUFFER, new Float32Array(this.normals), this.gl.STATIC_DRAW);
        this.gl.vertexAttribPointer(this.attNormal, 3, this.gl.FLOAT, false, 0, 0);

        this.texture_buffer = this.gl.createBuffer();
        this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.texture_buffer);
        this.gl.bufferData(this.gl.ARRAY_BUFFER, new Float32Array(this.textureCoords), this.gl.STATIC_DRAW);
        this.gl.vertexAttribPointer(this.attTexture, 2, this.gl.FLOAT, false, 0, 0);

        this.index_buffer = this.gl.createBuffer();
        this.gl.bindBuffer(this.gl.ELEMENT_ARRAY_BUFFER, this.index_buffer);
        this.gl.bufferData(this.gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(this.indices), this.gl.STATIC_DRAW);

        this.color_buffer = this.gl.createBuffer();
        this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.color_buffer);
        this.gl.bufferData(this.gl.ARRAY_BUFFER, new Float32Array(this.colors), this.gl.STATIC_DRAW);
        this.gl.vertexAttribPointer(this.attColor, 3, this.gl.FLOAT, false, 0, 0);

        //设置投射矩阵
        this.proj_matrix = this.getProjection(40, this.canvas.width / this.canvas.height, 1, 100);
        this.mov_matrix = new Float32Array([1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]);
        this.view_matrix = new Float32Array([1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]);
        this.view_matrix[13] = this.view_matrix[13] - this.modelHeight / 2;
        this.view_matrix[14] = this.view_matrix[14] - this.modelSize * 1.5;//视点沿z轴后移
        this.rotateX(this.view_matrix, Math.PI / 6);

        //获取uniform们的句柄
        this.Pmatrix = this.gl.getUniformLocation(shaderProgram, "Pmatrix");
        this.Vmatrix = this.gl.getUniformLocation(shaderProgram, "Vmatrix");
        this.Mmatrix = this.gl.getUniformLocation(shaderProgram, "Mmatrix");
        this.uniTexture = this.gl.getUniformLocation(shaderProgram, "texture");
        this.lightColor = this.gl.getUniformLocation(shaderProgram, "lightColor");
        this.lightDirection = this.gl.getUniformLocation(shaderProgram, "lightDirection");
        this.lightEnvironment = this.gl.getUniformLocation(shaderProgram, "lightEnvironment");

        this.gl.uniformMatrix4fv(this.Pmatrix, false, this.proj_matrix);
        this.gl.uniformMatrix4fv(this.Vmatrix, false, this.view_matrix);
        this.gl.uniformMatrix4fv(this.Mmatrix, false, this.mov_matrix);
        this.gl.uniform3fv(this.lightColor, new Float32Array([0.8, 0.8, 0.8]));
        this.gl.uniform3fv(this.lightDirection, new Float32Array([0, -1, -1]));
        this.gl.uniform3fv(this.lightEnvironment, new Float32Array([0.2, 0.2, 0.2]));

        //深度测试
        this.gl.enable(this.gl.DEPTH_TEST);
        this.gl.depthFunc(this.gl.LEQUAL);

        //设置图片数据
        this.initTexture(this.imgRes);

        this.render();
    }

    initTexture(source: string)
    {
        var img = new Image();
        img.src = source;
        img.onload = () =>
        {
            var texture = this.gl.createTexture();
            this.gl.pixelStorei(this.gl.UNPACK_FLIP_Y_WEBGL, 1);
            this.gl.activeTexture(this.gl.TEXTURE0);
            this.gl.bindTexture(this.gl.TEXTURE_2D, texture);
            this.gl.texImage2D(this.gl.TEXTURE_2D, 0, this.gl.RGBA, this.gl.RGBA, this.gl.UNSIGNED_BYTE, img);
            this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_MIN_FILTER, this.gl.LINEAR);
            this.gl.generateMipmap(this.gl.TEXTURE_2D);
            this.gl.uniform1i(this.uniTexture, 0);
        };
    }

    createSquare()
    {
        //制作着色器程序
        var vertShader = this.getShader("shader-vs");
        var fragShader = this.getShader("shader-fs");
        var shaderProgram = this.gl.createProgram();
        this.gl.attachShader(shaderProgram, vertShader);
        this.gl.attachShader(shaderProgram, fragShader);
        this.gl.linkProgram(shaderProgram);
        this.gl.useProgram(shaderProgram);

        //获取接口位置，并开启数组模式
        this.attPosition = this.gl.getAttribLocation(shaderProgram, "position");
        this.attTexture = this.gl.getAttribLocation(shaderProgram, "textureCoord");
        this.attColor = this.gl.getAttribLocation(shaderProgram, "color");
        this.attNormal = this.gl.getAttribLocation(shaderProgram, "normal");
        this.gl.enableVertexAttribArray(this.attPosition);
        this.gl.enableVertexAttribArray(this.attTexture);
        this.gl.enableVertexAttribArray(this.attColor);
        this.gl.enableVertexAttribArray(this.attNormal);

        //指定顶点坐标的数据源
        this.vertices = [
            -1, -1, -1, 1, -1, -1, -1, 1, -1, 1, 1, -1,
            -1, -1, 1, 1, -1, 1, -1, 1, 1, 1, 1, 1,
            -1, -1, -1, -1, 1, -1, -1, -1, 1, -1, 1, 1,
            1, -1, -1, 1, 1, -1, 1, -1, 1, 1, 1, 1,
            -1, -1, -1, 1, -1, -1, -1, -1, 1, 1, -1, 1,
            -1, 1, -1, 1, 1, -1, -1, 1, 1, 1, 1, 1
        ];
        this.vertex_buffer = this.gl.createBuffer();
        this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.vertex_buffer);
        this.gl.bufferData(this.gl.ARRAY_BUFFER, new Float32Array(this.vertices), this.gl.STATIC_DRAW);
        this.gl.vertexAttribPointer(this.attPosition, 3, this.gl.FLOAT, false, 0, 0);

        //指定顶点颜色数据源
        this.colors = [];
        for (var i = 0; i < this.vertices.length / 3; i++)
        {
            this.colors.push(1, 1, 1);
        }
        this.color_buffer = this.gl.createBuffer();
        this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.color_buffer);
        this.gl.bufferData(this.gl.ARRAY_BUFFER, new Float32Array(this.colors), this.gl.STATIC_DRAW);
        this.gl.vertexAttribPointer(this.attColor, 3, this.gl.FLOAT, false, 0, 0);

        //指定顶点法线数据源
        this.normals = [];
        var tmp = [[0, 0, -1], [0, 0, 1], [-1, 0, 0], [1, 0, 0], [0, -1, 0], [0, 1, 0]];
        for (var i = 0; i < tmp.length; i++)
        {
            for (var j = 0; j < 4; j++)
            {
                this.normals.push.apply(this.normals, tmp[i]);
            }
        }
        this.normal_buffer = this.gl.createBuffer();
        this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.normal_buffer);
        this.gl.bufferData(this.gl.ARRAY_BUFFER, new Float32Array(this.normals), this.gl.STATIC_DRAW);
        this.gl.vertexAttribPointer(this.attNormal, 3, this.gl.FLOAT, false, 0, 0);

        //指定贴图位置数据源
        this.textureCoords = [
            0.0, 1.0, 1.0, 1.0, 0.0, 0.0, 1.0, 0.0,
            0.0, 1.0, 1.0, 1.0, 0.0, 0.0, 1.0, 0.0,
            0.0, 1.0, 1.0, 1.0, 0.0, 0.0, 1.0, 0.0,
            0.0, 1.0, 1.0, 1.0, 0.0, 0.0, 1.0, 0.0,
            0.0, 1.0, 1.0, 1.0, 0.0, 0.0, 1.0, 0.0,
            0.0, 1.0, 1.0, 1.0, 0.0, 0.0, 1.0, 0.0
        ];
        this.texture_buffer = this.gl.createBuffer();
        this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.texture_buffer);
        this.gl.bufferData(this.gl.ARRAY_BUFFER, new Float32Array(this.textureCoords), this.gl.STATIC_DRAW);
        this.gl.vertexAttribPointer(this.attTexture, 2, this.gl.FLOAT, false, 0, 0);

        //构造索引
        this.indices = [];
        for (var i = 0; i < 24; i += 4)
        {
            this.indices.push(i + 0, i + 1, i + 2, i + 3, i + 2, i + 1);
        }
        this.index_buffer = this.gl.createBuffer();
        this.gl.bindBuffer(this.gl.ELEMENT_ARRAY_BUFFER, this.index_buffer);
        this.gl.bufferData(this.gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(this.indices), this.gl.STATIC_DRAW);
        
        //设置投射矩阵
        this.proj_matrix = this.getProjection(40, this.canvas.width / this.canvas.height, 1, 100);
        this.mov_matrix = new Float32Array([1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]);
        this.view_matrix = new Float32Array([1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]);
        this.view_matrix[14] = this.view_matrix[14] - 6;

        //获取uniform们的句柄
        this.Pmatrix = this.gl.getUniformLocation(shaderProgram, "Pmatrix");
        this.Vmatrix = this.gl.getUniformLocation(shaderProgram, "Vmatrix");
        this.Mmatrix = this.gl.getUniformLocation(shaderProgram, "Mmatrix");
        this.uniTexture = this.gl.getUniformLocation(shaderProgram, "texture");
        this.lightColor = this.gl.getUniformLocation(shaderProgram, "lightColor");
        this.lightDirection = this.gl.getUniformLocation(shaderProgram, "lightDirection");
        this.lightEnvironment = this.gl.getUniformLocation(shaderProgram, "lightEnvironment");

        this.gl.uniformMatrix4fv(this.Pmatrix, false, this.proj_matrix);
        this.gl.uniformMatrix4fv(this.Vmatrix, false, this.view_matrix);
        this.gl.uniformMatrix4fv(this.Mmatrix, false, this.mov_matrix);
        this.gl.uniform3fv(this.lightColor, new Float32Array([0.9, 0.9, 0.9]));
        this.gl.uniform3fv(this.lightDirection, new Float32Array([0, 0, -1]));
        this.gl.uniform3fv(this.lightEnvironment, new Float32Array([0.1, 0.1, 0.1]));

        //深度测试
        this.gl.enable(this.gl.DEPTH_TEST);
        this.gl.depthFunc(this.gl.LEQUAL);

        //设置图片数据
        this.initTexture("res/" + this.imgSource[this.imgIndex]);
    }

    getSphereData(radius: number = 1, widthSegments: number = 8, heightSegments: number = 6)
    {
        this.vertices = [];
        this.textureCoords = [];
        this.normals = [];
        this.indices = [];
        this.colors = [];

        widthSegments = Math.max( 3, Math.floor( widthSegments ));
        heightSegments = Math.max( 2, Math.floor( heightSegments ));

        var ix, iy;

        var index = 0;
        var grid = [];

        // generate vertices, normals and uvs
        for (iy = 0; iy <= heightSegments; iy++)
        {
            var verticesRow = [];
            var v = iy / heightSegments;
            for (ix = 0; ix <= widthSegments; ix++)
            {
                var u = ix / widthSegments;

                // vertex
                var vx = - radius * Math.cos(u * Math.PI * 2) * Math.sin(v * Math.PI);
                var vy = radius * Math.cos(v * Math.PI);
                var vz = radius * Math.sin(u * Math.PI * 2) * Math.sin(v * Math.PI);

                this.vertices.push(vx, vy, vz);

                // normal
                var nx = vx;
                var ny = vy;
                var nz = vz;
                var num: number = Math.sqrt(nx * nx + ny * ny + nz * nz);
                if (num > Number.MIN_VALUE)
                {
                    nx = nx / num;
                    ny = ny / num;
                    nz = nz / num;
                }
                else
                {
                    nx = 0;
                    ny = 0;
                    nz = 0;
                }
                this.normals.push(nx, ny, nz);

                // uv
                this.textureCoords.push(u, 1 - v);
                verticesRow.push(index++);
            }
            grid.push(verticesRow);
        }

        // indices
        for (iy = 0; iy < heightSegments; iy++)
        {
            for (ix = 0; ix < widthSegments; ix++)
            {
                var a = grid[iy][ix + 1];
                var b = grid[iy][ix];
                var c = grid[iy + 1][ix];
                var d = grid[iy + 1][ix + 1];

                if (iy !== 0) this.indices.push(a, b, d);
                if (iy !== heightSegments - 1) this.indices.push(b, c, d);
            }
        }

        //指定顶点颜色数据源
        for (var i = 0; i < this.vertices.length / 3; i++)
        {
            this.colors.push(1, 1, 1);
        }
    }

    createSphere(r: number, w: number, h: number)
    {
        //制作着色器程序
        var vertShader = this.getShader("shader-vs");
        var fragShader = this.getShader("shader-fs");
        var shaderProgram = this.gl.createProgram();
        this.gl.attachShader(shaderProgram, vertShader);
        this.gl.attachShader(shaderProgram, fragShader);
        this.gl.linkProgram(shaderProgram);
        this.gl.useProgram(shaderProgram);

        //获取接口位置，并开启数组模式
        this.attPosition = this.gl.getAttribLocation(shaderProgram, "position");
        this.attTexture = this.gl.getAttribLocation(shaderProgram, "textureCoord");
        this.attColor = this.gl.getAttribLocation(shaderProgram, "color");
        this.attNormal = this.gl.getAttribLocation(shaderProgram, "normal");
        this.gl.enableVertexAttribArray(this.attPosition);
        this.gl.enableVertexAttribArray(this.attTexture);
        this.gl.enableVertexAttribArray(this.attColor);
        this.gl.enableVertexAttribArray(this.attNormal);

        this.getSphereData(r, w, h);

        this.vertex_buffer = this.gl.createBuffer();
        this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.vertex_buffer);
        this.gl.bufferData(this.gl.ARRAY_BUFFER, new Float32Array(this.vertices), this.gl.STATIC_DRAW);
        this.gl.vertexAttribPointer(this.attPosition, 3, this.gl.FLOAT, false, 0, 0);

        this.normal_buffer = this.gl.createBuffer();
        this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.normal_buffer);
        this.gl.bufferData(this.gl.ARRAY_BUFFER, new Float32Array(this.normals), this.gl.STATIC_DRAW);
        this.gl.vertexAttribPointer(this.attNormal, 3, this.gl.FLOAT, false, 0, 0);

        this.texture_buffer = this.gl.createBuffer();
        this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.texture_buffer);
        this.gl.bufferData(this.gl.ARRAY_BUFFER, new Float32Array(this.textureCoords), this.gl.STATIC_DRAW);
        this.gl.vertexAttribPointer(this.attTexture, 2, this.gl.FLOAT, false, 0, 0);

        this.index_buffer = this.gl.createBuffer();
        this.gl.bindBuffer(this.gl.ELEMENT_ARRAY_BUFFER, this.index_buffer);
        this.gl.bufferData(this.gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(this.indices), this.gl.STATIC_DRAW);
        
        this.color_buffer = this.gl.createBuffer();
        this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.color_buffer);
        this.gl.bufferData(this.gl.ARRAY_BUFFER, new Float32Array(this.colors), this.gl.STATIC_DRAW);
        this.gl.vertexAttribPointer(this.attColor, 3, this.gl.FLOAT, false, 0, 0);

        //设置投射矩阵
        this.proj_matrix = this.getProjection(40, this.canvas.width / this.canvas.height, 1, 100);
        this.mov_matrix = new Float32Array([1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]);
        this.view_matrix = new Float32Array([1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]);
        this.view_matrix[13] = this.view_matrix[13] - r;
        this.view_matrix[14] = this.view_matrix[14] - 6;//视点沿z轴后移
        this.rotateX(this.view_matrix, Math.PI / 6);

        //获取uniform们的句柄
        this.Pmatrix = this.gl.getUniformLocation(shaderProgram, "Pmatrix");
        this.Vmatrix = this.gl.getUniformLocation(shaderProgram, "Vmatrix");
        this.Mmatrix = this.gl.getUniformLocation(shaderProgram, "Mmatrix");
        this.uniTexture = this.gl.getUniformLocation(shaderProgram, "texture");
        this.lightColor = this.gl.getUniformLocation(shaderProgram, "lightColor");
        this.lightDirection = this.gl.getUniformLocation(shaderProgram, "lightDirection");
        this.lightEnvironment = this.gl.getUniformLocation(shaderProgram, "lightEnvironment");

        this.gl.uniformMatrix4fv(this.Pmatrix, false, this.proj_matrix);
        this.gl.uniformMatrix4fv(this.Vmatrix, false, this.view_matrix);
        this.gl.uniformMatrix4fv(this.Mmatrix, false, this.mov_matrix);
        this.gl.uniform3fv(this.lightColor, new Float32Array([0.8, 0.8, 0.8]));
        this.gl.uniform3fv(this.lightDirection, new Float32Array([0, -1, -1]));
        this.gl.uniform3fv(this.lightEnvironment, new Float32Array([0.2, 0.2, 0.2]));

        //深度测试
        this.gl.enable(this.gl.DEPTH_TEST);
        this.gl.depthFunc(this.gl.LEQUAL);

        //设置图片数据
        this.initTexture("res/" + this.imgSource[this.imgIndex]);
    }

    getCylinderData(radius: number, height: number, n: number)
    {
        this.vertices = [];
        this.textureCoords = [];
        this.normals = [];
        this.indices = [];
        this.colors = [];
        
        //上底面
        this.vertices.push(0, height, 0);
        this.textureCoords.push(0.5, 0.5);
        this.normals.push(0, 1, 0);
        for (var i = 0; i < n; i++)
        {
            var r = Math.PI * 2 * i / n;
            var x = Math.sin(r) * radius;
            var y = height;
            var z = Math.cos(r) * radius;

            this.vertices.push(x, y, z);
            this.textureCoords.push(0.5 + x / 2 / radius, 0.5 - z / 2 / radius);
            this.normals.push(0, 1, 0);
        }
        for (var i = 0; i < n; i++)
        {
            this.indices.push(0, 1 + i, 1 + (i + 1) % n);
        }
        //下底面
        this.vertices.push(0, 0, 0);
        this.textureCoords.push(0.5, 0.5);
        this.normals.push(0, -1, 0);
        for (var i = 0; i < n; i++)
        {
            var r = Math.PI * 2 * i / n;
            var x = Math.sin(r) * radius;
            var y = 0;
            var z = Math.cos(r) * radius;

            this.vertices.push(x, y, z);
            this.textureCoords.push(0.5 + x / 2 / radius, 0.5 + z / 2 / radius);
            this.normals.push(0, -1, 0);
        }
        for (var i = 0; i < n; i++)
        {
            this.indices.push(n + 1, n + 2 + i, n + 2 + (i + 1) % n);
        }
        //侧面
        for (var i = 0; i <= n; i++)
        {
            var r = Math.PI * 2 * i / n;
            var x = Math.sin(r) * radius;
            var y1 = height;
            var y2 = 0;
            var z = Math.cos(r) * radius;

            this.vertices.push(x, y1, z);
            this.textureCoords.push(i / n, 1);
            this.normals.push(x, 0, z);

            this.vertices.push(x, y2, z);
            this.textureCoords.push(i / n, 0);
            this.normals.push(x, 0, z);
        }
        for (var i = 0; i < n; i++)
        {
            var ii = i * 2;
            this.indices.push(n * 2 + 2 + ii, n * 2 + 2 + ii + 1, n * 2 + 2 + ii + 2);
            this.indices.push(n * 2 + 2 + ii + 3, n * 2 + 2 + ii + 2, n * 2 + 2 + ii + 1);
        }

        //指定顶点颜色数据源
        for (var i = 0; i < this.vertices.length / 3; i++)
        {
            this.colors.push(1, 1, 1);
        }
        //for (var i = 0; i < this.vertices.length / 36; i++)
        //{
        //    this.colors.push(0, 1, 0, 1, 0, 0, 1, 0, 0);
        //    this.colors.push(0, 1, 0, 0, 0, 1, 0, 0, 1);
        //    this.colors.push(1, 0, 0, 1, 0, 0, 0, 0, 1);
        //    this.colors.push(0, 0, 1, 0, 0, 1, 1, 0, 0);
        //}
    }

    createCylinder(radius: number, height: number, n: number)
    {
        //制作着色器程序
        var vertShader = this.getShader("shader-vs");
        var fragShader = this.getShader("shader-fs");
        var shaderProgram = this.gl.createProgram();
        this.gl.attachShader(shaderProgram, vertShader);
        this.gl.attachShader(shaderProgram, fragShader);
        this.gl.linkProgram(shaderProgram);
        this.gl.useProgram(shaderProgram);

        //获取接口位置，并开启数组模式
        this.attPosition = this.gl.getAttribLocation(shaderProgram, "position");
        this.attTexture = this.gl.getAttribLocation(shaderProgram, "textureCoord");
        this.attColor = this.gl.getAttribLocation(shaderProgram, "color");
        this.attNormal = this.gl.getAttribLocation(shaderProgram, "normal");
        this.gl.enableVertexAttribArray(this.attPosition);
        this.gl.enableVertexAttribArray(this.attTexture);
        this.gl.enableVertexAttribArray(this.attColor);
        this.gl.enableVertexAttribArray(this.attNormal);

        this.getCylinderData(radius, height, n);

        this.vertex_buffer = this.gl.createBuffer();
        this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.vertex_buffer);
        this.gl.bufferData(this.gl.ARRAY_BUFFER, new Float32Array(this.vertices), this.gl.STATIC_DRAW);
        this.gl.vertexAttribPointer(this.attPosition, 3, this.gl.FLOAT, false, 0, 0);

        this.normal_buffer = this.gl.createBuffer();
        this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.normal_buffer);
        this.gl.bufferData(this.gl.ARRAY_BUFFER, new Float32Array(this.normals), this.gl.STATIC_DRAW);
        this.gl.vertexAttribPointer(this.attNormal, 3, this.gl.FLOAT, false, 0, 0);

        this.texture_buffer = this.gl.createBuffer();
        this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.texture_buffer);
        this.gl.bufferData(this.gl.ARRAY_BUFFER, new Float32Array(this.textureCoords), this.gl.STATIC_DRAW);
        this.gl.vertexAttribPointer(this.attTexture, 2, this.gl.FLOAT, false, 0, 0);

        this.index_buffer = this.gl.createBuffer();
        this.gl.bindBuffer(this.gl.ELEMENT_ARRAY_BUFFER, this.index_buffer);
        this.gl.bufferData(this.gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(this.indices), this.gl.STATIC_DRAW);
        
        this.color_buffer = this.gl.createBuffer();
        this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.color_buffer);
        this.gl.bufferData(this.gl.ARRAY_BUFFER, new Float32Array(this.colors), this.gl.STATIC_DRAW);
        this.gl.vertexAttribPointer(this.attColor, 3, this.gl.FLOAT, false, 0, 0);

        //设置投射矩阵
        this.proj_matrix = this.getProjection(40, this.canvas.width / this.canvas.height, 1, 100);
        this.mov_matrix = new Float32Array([1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]);
        this.view_matrix = new Float32Array([1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]);
        this.view_matrix[13] = this.view_matrix[13] - height / 2;
        this.view_matrix[14] = this.view_matrix[14] - 6;//视点沿z轴后移
        this.rotateX(this.view_matrix, Math.PI / 6);

        //获取uniform们的句柄
        this.Pmatrix = this.gl.getUniformLocation(shaderProgram, "Pmatrix");
        this.Vmatrix = this.gl.getUniformLocation(shaderProgram, "Vmatrix");
        this.Mmatrix = this.gl.getUniformLocation(shaderProgram, "Mmatrix");
        this.uniTexture = this.gl.getUniformLocation(shaderProgram, "texture");
        this.lightColor = this.gl.getUniformLocation(shaderProgram, "lightColor");
        this.lightDirection = this.gl.getUniformLocation(shaderProgram, "lightDirection");
        this.lightEnvironment = this.gl.getUniformLocation(shaderProgram, "lightEnvironment");

        this.gl.uniformMatrix4fv(this.Pmatrix, false, this.proj_matrix);
        this.gl.uniformMatrix4fv(this.Vmatrix, false, this.view_matrix);
        this.gl.uniformMatrix4fv(this.Mmatrix, false, this.mov_matrix);
        this.gl.uniform3fv(this.lightColor, new Float32Array([0.8, 0.8, 0.8]));
        this.gl.uniform3fv(this.lightDirection, new Float32Array([0, -1, -1]));
        this.gl.uniform3fv(this.lightEnvironment, new Float32Array([0.2, 0.2, 0.2]));

        //深度测试
        this.gl.enable(this.gl.DEPTH_TEST);
        this.gl.depthFunc(this.gl.LEQUAL);

        //设置图片数据
        this.initTexture("res/" + this.imgSource[this.imgIndex]);
    }

    getShader(id: string)
    {
        var shaderScript = <HTMLScriptElement>document.getElementById(id);
        if (!shaderScript)
        {
            return null;
        }
        var str = "";
        var k = shaderScript.firstChild;
        while (k)
        {
            if (k.nodeType == 3)
            {
                str += k.textContent;
            }
            k = k.nextSibling;
        }
        var shader;
        if (shaderScript.type == "x-shader/x-fragment")
        {
            shader = this.gl.createShader(this.gl.FRAGMENT_SHADER);
        }
        else if (shaderScript.type == "x-shader/x-vertex")
        {
            shader = this.gl.createShader(this.gl.VERTEX_SHADER);
        }
        else
        {
            return null;
        }
        this.gl.shaderSource(shader, str);
        this.gl.compileShader(shader);
        if (!this.gl.getShaderParameter(shader, this.gl.COMPILE_STATUS))
        {
            alert("compile" + this.gl.getShaderInfoLog(shader));
            return null;
        }
        return shader;
    }

    getProjection(angle: number, a: number, zMin: number, zMax: number): Float32Array
    {
        var ang = Math.tan((angle * 0.5) * Math.PI / 180);
        return new Float32Array([
            0.5 / ang, 0, 0, 0,
            0, 0.5 * a / ang, 0, 0,
            0, 0, -(zMax + zMin) / (zMax - zMin), -1,
            0, 0, (-2 * zMax * zMin) / (zMax - zMin), 0
        ]);
    }

    rotateX(m: Float32Array, radian: number)
    {
        var c = Math.cos(radian);
        var s = Math.sin(radian);
        var mv1 = m[1], mv5 = m[5], mv9 = m[9];

        m[1] = m[1] * c - m[2] * s;
        m[5] = m[5] * c - m[6] * s;
        m[9] = m[9] * c - m[10] * s;
        m[2] = m[2] * c + mv1 * s;
        m[6] = m[6] * c + mv5 * s;
        m[10] = m[10] * c + mv9 * s;
    }

    rotateY(m: Float32Array, radian: number)
    {
        var c = Math.cos(radian);
        var s = Math.sin(radian);
        var mv0 = m[0], mv4 = m[4], mv8 = m[8];

        m[0] = c * m[0] + s * m[2];
        m[4] = c * m[4] + s * m[6];
        m[8] = c * m[8] + s * m[10];
        m[2] = c * m[2] - s * mv0;
        m[6] = c * m[6] - s * mv4;
        m[10] = c * m[10] - s * mv8;
    }

    rotateZ(m: Float32Array, radian: number)
    {
        var c = Math.cos(radian);
        var s = Math.sin(radian);
        var mv0 = m[0], mv4 = m[4], mv8 = m[8];

        m[0] = c * m[0] - s * m[1];
        m[4] = c * m[4] - s * m[5];
        m[8] = c * m[8] - s * m[9];
        m[1] = c * m[1] + s * mv0;
        m[5] = c * m[5] + s * mv4;
        m[9] = c * m[9] + s * mv8;
    }

}

window.onload = () =>
{
    var renderer = new glRenderer();
    renderer.start();
};